package edu.mit.compilers.lowercode.convertor;

import edu.mit.compilers.ir.common.IR;
import edu.mit.compilers.ir.common.IRVisitor;
import edu.mit.compilers.lowercode.SymbolTable;
import edu.mit.compilers.lowercode.ThreeAddressCodesInfo;
import edu.mit.compilers.lowercode.code.MethodBeginCode;
import edu.mit.compilers.lowercode.code.MethodLeaveCode;
import edu.mit.compilers.lowercode.code.ThreeAddressCode;
import edu.mit.compilers.lowercode.code.ThreeAddressCodeList;

import java.util.ArrayList;
import java.util.HashMap;

public class LowerCodeConvertor {

    public ThreeAddressCodesInfo convertToLowCode(IR tree) throws CloneNotSupportedException {
        assert tree.getParent() == null;
        ThreeAddressCodesInfo result = new ThreeAddressCodesInfo();

        // previous process before conversion
        ArrayList<IRVisitor<Void>> processors = new ArrayList<>();
        processors.add(new SymbolTableSetter(result.symbolTable));
        PreProcessors preProcessors = new PreProcessors(processors);
        preProcessors.process(tree);

        // convert
        LowerCodeConvertorVisitor visitor = new LowerCodeConvertorVisitor(result.symbolTable);
        tree.accept(visitor);
        ThreeAddressCodeList codes = tree.getLowerCodes();

        // fill label
        new CodeLabelSetter().process(codes);
        codes = new EmptyCodeFolder().process(codes);
        result.method2Codes = SplitCodesByMethod(result.symbolTable, codes);
        return result;
    }

    // split codes into many codes group by method name
    private HashMap<String, ThreeAddressCodeList> SplitCodesByMethod(SymbolTable symbolTable, ThreeAddressCodeList codes) {
        HashMap<String, ThreeAddressCodeList> ret = new HashMap<>();
        String methodName = null;
        ThreeAddressCodeList methodCodes = new ThreeAddressCodeList();

        for (ThreeAddressCode code : codes) {
            if (code instanceof MethodBeginCode) {
                assert methodCodes.isNull();
                assert methodName == null : "method " + methodName + " has not ended";
                methodName = ((MethodBeginCode) code).getMethodName();
                assert symbolTable.getMethodInfo(methodName) != null : methodName + "not found in symbol table";
            }
            methodCodes.append(code);
            if (code instanceof MethodLeaveCode) {
                assert methodName != null : "method " + null + " has not began";
                assert !methodCodes.isNull() : "method " + methodName + " has no codes";
                ret.put(methodName, methodCodes);
                methodCodes = new ThreeAddressCodeList();
                methodName = null;
            }
        }
        assert methodCodes.isNull();
        assert methodName == null : "method " + methodName + " has not ended";
        return ret;
    }
}
